/*******************************************************************************
** Pretty much information to write this driver was taken from libftdi source **
** code. The commands that libftdi sends to libusb where translated to        **
** to kernel's usb commands base on the article wich explains how to write    **
** libftdi code based on kernel code                                          **
** http://www.linuxjournal.com/article/7466?page=0,1                          **
*******************************************************************************/

#include <linux/usb.h>    /* used in almost everything */
#include <linux/ppdev.h>  /* parport ioctl commands definitions */

#include <linux/errno.h>  /* error numbers */
#include <linux/slab.h>   /* kzalloc */

/*== Debugging functions =====================================================*/

#ifndef NDEBUG

#include <linux/ioport.h>
#include <asm/io.h>

#define PP_PORT 0x378
#define PP_DATA PP_PORT

inline void debug_request_port(void){
	check_region(PP_PORT, 3);
	request_region(PP_PORT, 3, "ftdiblaster");
}
inline void debug_release_port(void){
	release_region(PP_PORT,3);
}
inline void debug_toggle_on(char bytes){
	char pp = inb(PP_DATA);
	pp |= bytes;
	outb(pp, PP_DATA);
}
inline void debug_toggle_off(char bytes){
	char pp = inb(PP_DATA);
	pp &= ~bytes;
	outb(pp, PP_DATA);
}

#else

#define debug_request_port()
#define debug_release_port()
#define debug_toggle_on(x)
#define debug_toggle_off(x)

#endif

/*== FTDI definitions ========================================================*/
/* from ftdi.h (libftdi) */

#define FTDIBLASTER_VENDOR_ID	0x0403
#define FTDIBLASTER_PRODUCT_ID	0x6010

enum ftdi_interface {
	INTERFACE_ANY = 0,
	INTERFACE_A   = 1,
	INTERFACE_B   = 2,
	INTERFACE_C   = 3,
	INTERFACE_D   = 4
};

#define USB_ENDPOINT_IN         0x80
#define USB_ENDPOINT_OUT        0x00
#define FTDI_DEVICE_IN_REQTYPE  (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN)
#define FTDI_DEVICE_OUT_REQTYPE (USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT)

#define SIO_READ_PINS_REQUEST     0x0C
#define SIO_SET_BITMODE_REQUEST   0x0B
#define SIO_SET_BAUDRATE_REQUEST  SIO_SET_BAUD_RATE
#define SIO_SET_BAUD_RATE         3

#define FTDI_MAX_BAUDRATE         0 /* prescaler for 3 MHz baudrate */

/*== ftdiblaster parallelport emulation typedefs =============================*/

typedef struct {
	unsigned char b0 : 1;
	unsigned char b1 : 1;
	unsigned char b2 : 1;
	unsigned char b3 : 1;
	unsigned char b4 : 1;
	unsigned char b5 : 1;
	unsigned char b6 : 1;
	unsigned char b7 : 1;
} bitfield;
typedef union {
	unsigned char byte;
	bitfield      bit;
} t_data;
typedef union {
	unsigned char byte;
	bitfield      bit;
	struct {
		unsigned char          : 3;
		unsigned char error    : 1;
		unsigned char select   : 1;
		unsigned char paperout : 1;
		unsigned char ack      : 1;
		unsigned char busy     : 1;
	} pin;
} t_status;
typedef union {
	unsigned char byte;
	bitfield      bit;
	struct {
		unsigned char strobe   : 1;
		unsigned char linefeed : 1;
		unsigned char reset    : 1;
		unsigned char select   : 1;
		unsigned char          : 4;
	} pin;
} t_control;

/*== ftdiblaster driver private data =========================================*/

#define BUFFER_SIZE 4096

#define get_interface(interface) \
	((interface)->cur_altsetting->desc.bInterfaceNumber + 'A')

struct ftdiblaster_privatedata {
	struct usb_device     *udev;        /* the usb device for this device */
	struct usb_interface  *interface;   /* the interface for this device */
	
	__u8                  bulk_in;      /* bulk in endpoint's address */
	__u8                  bulk_out;     /* bulk out endpoint's address */
	                                    /* (not used) */
	
	int                   write_count;  /* writing buffer to speed up */
	char                  write_buffer[BUFFER_SIZE];
	
	t_data                data;         /* parallel port emulation data */
	t_status              status;
	t_control             control;      /* not used */
	t_data                connector;    /* our connector's data */
};
static struct usb_driver ftdiblaster_driver;

/*== USB specific information ================================================*/
/* Get a minor range for your devices from the usb maintainer */
#define USB_MINOR_BASE  192
#define USB_TIMEOUT     0 /* wait forever */

/* Table of devices that work with this driver */
static const struct usb_device_id ftdiblaster_table[] = {
	{ USB_DEVICE(FTDIBLASTER_VENDOR_ID, FTDIBLASTER_PRODUCT_ID) },
	{ }					/* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, ftdiblaster_table);

/*== Driver File Operations Functions ========================================*/
static int ftdiblaster_open(struct inode *inode, struct file *file){
	struct ftdiblaster_privatedata *data;
	struct usb_interface *interface;
	int subminor;
	int retval = 0;
	unsigned short bitmode;
	
	subminor = iminor(inode);
	
	interface = usb_find_interface(&ftdiblaster_driver, subminor);
	if (!interface) {
		err("%s - error, can't find device for minor %d",
		     __func__, subminor);
		return -ENODEV;
	}
	
	data = usb_get_intfdata(interface);
	if (!data) {
		return -ENODEV;
	}
	
	retval = usb_autopm_get_interface(interface);
	if (retval) {
		return retval;
	}
	
	// from libftdi's "ftdi.c" function ftdi_set_bitmode
	bitmode = 0x3F; // low byte: bitmask
	bitmode |= (0x01 << 8); // high byte: bitbang mode
	usb_control_msg(data->udev,
			usb_sndctrlpipe(data->udev, 0),
			SIO_SET_BITMODE_REQUEST,
			FTDI_DEVICE_OUT_REQTYPE,
			bitmode,
			INTERFACE_B,
			NULL,
			0,
			USB_TIMEOUT);
	
	// from libftdi's "ftdi.c" function ftdi_set_baudrate
	usb_control_msg(data->udev,
	                usb_sndctrlpipe(data->udev, 0),
	                SIO_SET_BAUDRATE_REQUEST,
	                FTDI_DEVICE_OUT_REQTYPE,
	                FTDI_MAX_BAUDRATE,
	                INTERFACE_B,
	                NULL,
	                0,
	                USB_TIMEOUT);
	
	// save our object in the file's private structure
	file->private_data = data;
	
	return 0;
}
static int ftdiblaster_release(struct inode *inode, struct file *file){
	struct ftdiblaster_privatedata *data;
	
	data = file->private_data;
	if (data == NULL)
		return -ENODEV;
	
	// allow the device to be autosuspended
	if (data->interface)
		usb_autopm_put_interface(data->interface);
	
	return 0;
}

static char ftdiblaster_read_pins(struct ftdiblaster_privatedata *data){
	int retval = 0;
	char pins;
	
	debug_toggle_on(0xF0);
	
	// from libftdi's "ftdi.c" function ftdi_read_pins
	retval = usb_control_msg(data->udev,
	                         usb_rcvctrlpipe(data->udev, 0),
	                         SIO_READ_PINS_REQUEST,
	                         FTDI_DEVICE_IN_REQTYPE,
	                         0,
	                         INTERFACE_B,
	                         &pins,
	                         1,
	                         USB_TIMEOUT);
	
	debug_toggle_off(0xF0);
	
	if(retval != 1){
		dev_info(&data->interface->dev,
		         "USB ftdiblaster%c: reading FAILED!",
		         get_interface(data->interface));
		return 0;
	}
	
	return pins;
}

static ssize_t ftdiblaster_write_buffer(struct ftdiblaster_privatedata *data,
                                        char *buffer, size_t count){
	int retval = 0;
	int actual_count;
	
	debug_toggle_on(0x0F);
	
	// from libftdi's "ftdi.c" function ftdi_write_data
	retval = usb_bulk_msg(data->udev,
	                      usb_sndbulkpipe(data->udev, data->bulk_in),
	                      buffer,
	                      count,
	                      &actual_count,
	                      USB_TIMEOUT);
	
	debug_toggle_off(0x0F);
	
	if(retval){
		dev_info(&data->interface->dev,
		         "USB ftdiblaster%c: writing FAILED!",
		         get_interface(data->interface));
	}
	
	if(actual_count != count){
		dev_info(&data->interface->dev,
		         "USB ftdiblaster%c: reading INCOMPLETE!!!",
		         get_interface(data->interface));
	}
	
	return retval ? retval : actual_count;
}

static void ftdiblaster_flush_buffer(struct ftdiblaster_privatedata *data){
	// note: the sync pinc should not be needed anymore since the baudrate
	//       was set to its highest possible value
	
	// add an extra byte to the end of the buffer to toggle our sync pin
	data->connector.bit.b5 = 1;
	data->write_buffer[data->write_count++] = data->connector.byte;
	
	ftdiblaster_write_buffer(data,
	                         data->write_buffer,
	                         data->write_count);
	data->write_count = 0;
	
	// add an extra byte to the begining of the buffer toggle our sync pin
	data->connector.bit.b5 = 0;
	data->write_buffer[data->write_count++] = data->connector.byte;
}

static ssize_t ftdiblaster_read(struct file *file,
                                char __user * buffer, size_t count,
                                loff_t *ppos){
	/* quartus uses ioctl only! */
	return -EINVAL;
}

static ssize_t ftdiblaster_write(struct file *file,
                                 const char __user * user_buffer, size_t count,
                                 loff_t *ppos){
	/* quartus uses ioctl only! */
	return -EINVAL;
}

static long ftdiblaster_ioctl(struct file *file,
                              unsigned int cmd, unsigned long arg){
	
	struct ftdiblaster_privatedata *data;
	
	data = file->private_data;
	
	// unclaimed ioctl (this are ppdev things for shared ports)
	switch(cmd){
		case PPCLAIM: case PPEXCL:
			// assume everything is allright
			return 0;
		
		// not emulated
		case PPSETMODE:  case PPGETMODE:  case PPSETPHASE:
		case PPGETPHASE: case PPGETMODES: case PPSETFLAGS:
		case PPGETFLAGS:
			return 0;
	}
	
	// claimed ioctl (this are ppdev things for shared ports)
	switch(cmd){	
		case PPRSTATUS:
			// flush the writing buffer
			if(data->write_count){
				ftdiblaster_flush_buffer(data);
			}
			
			// wait till our sync pin tells us everything is ok
			do{
				data->connector.byte = ftdiblaster_read_pins(data);
			}while(data->connector.bit.b5 != 1);
			
			// build the status (comments apply for the jtag mode)
			data->status.pin.error  = 0;                       // bit3 = not vcc
			data->status.pin.select = data->connector.bit.b7;  // bit4 = not connected
			data->status.pin.ack    = data->data.bit.b4;       // bit6 = Data 4
			data->status.pin.busy   = !data->connector.bit.b6; // bit7 = TDO
			
			// to Quartus
			copy_to_user((char*)arg, &data->status.byte, 1);
			
			return 0;
			
		case PPWDATA:
			//debug_toggle_on(0xF0);
			
			// from Quartus
			copy_from_user(&data->data.byte, (char*)arg, 1);
			
			// build the connector
			data->connector.bit.b0 = data->data.bit.b0;
			data->connector.bit.b1 = data->data.bit.b1;
			data->connector.bit.b2 = data->data.bit.b2;
			data->connector.bit.b3 = data->data.bit.b3;
			data->connector.bit.b4 = data->data.bit.b6;
			data->connector.bit.b5 = 0;
			
			// output the connector
			data->write_buffer[data->write_count++] =
			                                   data->connector.byte;
			
			//debug_toggle_on(0xF0);
			
			// compare against BUFFER_SIZE-1
			// (the -1 is because of the sync pin)
			// (read ftdiblaster_flush_buffer)
			if(data->write_count >= BUFFER_SIZE-1){
				ftdiblaster_flush_buffer(data);
			}
			
			return 0;
			
		// not emulated
		case PPRDATA:   case PPRCONTROL: case PPYIELD:
		case PPRELEASE: case PPWCONTROL: case PPFCONTROL:
		case PPDATADIR: case PPNEGOT:    case PPWCTLONIRQ:
		case PPCLRIRQ:  case PPSETTIME:  case PPGETTIME:
			return 0;
	}
	
	return -EINVAL;
}

static const struct file_operations ftdiblaster_fops = {
	.owner =		THIS_MODULE,
	
	.open =			ftdiblaster_open,
	.release =		ftdiblaster_release,
	
	.read =			ftdiblaster_read,
	.write =		ftdiblaster_write,
	
	.unlocked_ioctl =	ftdiblaster_ioctl,
};

/*== Driver USB Integration ==================================================*/

/*
 * usb class driver info in order to get a minor number from the usb core,
 * and to have the device registered with the driver core
 */
static struct usb_class_driver ftdiblaster_class = {
	.name =		"ftdiblaster%d",
	.fops =		&ftdiblaster_fops,
	.minor_base =	USB_MINOR_BASE,
};

/* probe is responsable for handling usb device's auto-detection */
/* it tells the kernel: yes! i'm the driver who will handle this device! */
/* based on the vendor id and the product id at the beginning of this file */
static int ftdiblaster_probe(struct usb_interface *interface,
                             const struct usb_device_id *id)
{
	struct ftdiblaster_privatedata *data;
	
	/* allocate memory for our device state and initialize it */
	data = kzalloc(sizeof(*data), GFP_KERNEL);
	if(!data) {
		err("Out of memory");
		return -ENOMEM;
	}
	
	/* set the interface and the endpoints */
	data->udev = usb_get_dev(interface_to_usbdev(interface));
	data->interface = interface;
	
	/* Interface B endpoints, from libftdi function ftdi_set_interface */
	data->bulk_in  = 0x04;
	data->bulk_out = 0x83; /* not used right now */
	
	/* save our data pointer in this interface device */
	usb_set_intfdata(interface, data);
	
	/* we can register the device now, as it is ready */
	if ( usb_register_dev(interface, &ftdiblaster_class) ) {
		/* something prevented us from registering this driver */
		err("Not able to get a minor for this device.");
		usb_set_intfdata(interface, NULL);
		kfree(data);
		return 0;
	}
	
	/* let the user know what node this device is now attached to */
	dev_info(&interface->dev,
		 "USB ftdiblaster device now attached to ftdiblaster%d, "
		 "Interface: %c",
		 interface->minor, get_interface(interface));
	
	return 0;
}

static void ftdiblaster_disconnect(struct usb_interface *interface)
{
	struct ftdiblaster_privatedata *data;
	int minor = interface->minor;
	
	data = usb_get_intfdata(interface);
	usb_set_intfdata(interface, NULL);
	
	/* give back our minor */
	usb_deregister_dev(interface, &ftdiblaster_class);
	
	/* free the memory used for our device state */
	kfree(data);
	
	dev_info(&interface->dev,
	         "USB ftdiblaster%d, Interface: %c now disconnected",
	         minor, get_interface(interface));
}

static struct usb_driver ftdiblaster_driver = {
	.name =		"ftdiblaster",
	
	.probe =	ftdiblaster_probe,
	.disconnect =	ftdiblaster_disconnect,
	
	.id_table =	ftdiblaster_table,
	
	.supports_autosuspend = 1,
};

/*== kernel module initialization and exit ===================================*/

static int __init ftdiblaster_init(void)
{
	int result;
	
	debug_request_port();
	
	/* register this driver with the USB subsystem */
	result = usb_register(&ftdiblaster_driver);
	if (result)
		err("usb_register failed. Error number %d", result);
	
	return result;
}

static void __exit ftdiblaster_exit(void)
{
	debug_release_port();
	
	/* deregister this driver with the USB subsystem */
	usb_deregister(&ftdiblaster_driver);
}

module_init(ftdiblaster_init);
module_exit(ftdiblaster_exit);

MODULE_LICENSE("GPL");
